今天要聊到的是 Task 把超過自己承載能力的任務放入 TP 交給別條 thread 執行的過程發生了甚麼 ?
參考函數 : ThreadPool.UnsafeQueueCustomWorkItem
翻看原始碼 ThreadPool.UnsafeQueueCustomWorkItem
[SecurityCritical]
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
Contract.Assert(null != workItem);
EnsureVMInitialized();
//
// Enqueue needs to be protected from ThreadAbort
//
try { }
finally
{
ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
}
}
先來看參數
ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false);
CompletionActionInvoker
也就是原始碼的 IThreadPoolWorkItem workItem
在 Task 的第一集就有提到, 創建時會把待完成任務存入, 並且留下介面供別人觸發此任務。
再來看看 ThreadPoolGlobals
internal static class ThreadPoolGlobals
{
//Per-appDomain quantum (in ms) for which the thread keeps processing
//requests in the current domain.
public static uint tpQuantum = 30U;
public static int processorCount = Environment.ProcessorCount;
public static bool tpHosted = ThreadPool.IsThreadPoolHosted();
public static volatile bool vmTpInitialized;
public static bool enableWorkerTracking;
[SecurityCritical]
public static ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue();
[System.Security.SecuritySafeCritical] // static constructors should be safe to call
static ThreadPoolGlobals()
{
}
}
發現這是一個 static 的變數, 其成員變數有一個 workQueue
而之前所說的, 把待完成任務放入 TP 由別條 thread 執行, 原來指的就是把任務 push 進 workQueue
這個資料結構。
此刻, 相信碰過 linux 的讀者會很快了聯想起 workQueue
這個資料結構所代表的意義, 所以我心急如焚的察看Enqueue
method。
[SecurityCritical]
public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)
{
// forceGlobal=false ,獲取當前 thread 的任務列表
ThreadPoolWorkQueueThreadLocals tl = null;
if (!forceGlobal)
tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
if (loggingEnabled)
System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
// 任務列表不為空, 加入新任務
if (null != tl)
{
tl.workStealingQueue.LocalPush(callback);
}
else
{
// 任務列表為空, 加入新任務
QueueSegment head = queueHead;
while (!head.TryEnqueue(callback))
{
Interlocked.CompareExchange(ref head.Next, new QueueSegment(), null);
while (head.Next != null)
{
Interlocked.CompareExchange(ref queueHead, head.Next, head);
head = queueHead;
}
}
}
// 調用 worker thread 進行 schedule (此段程式碼利用 dll 引入)
EnsureThreadRequested();
}
除了利用 CAS 方法 lock-free 的把存放進屬於 thread 的私人任務存放區外
果然找到了 , worker Thread 的調用, 以下簡單聊聊我對這段的理解。
我會先說說何謂 linux 中的 workQueue
, 因為在這段程式碼最後, 我們發現 .Net 向外引用了 worker thread
而這是外面的程式碼, 我看不到, 所以此刻只能用 workQueue
來猜測其功能。接著說說我對 .Net ThreadPool
的理解。
workQueue
以 linux 的workQueue
為例(注:我程度不足,僅簡單說說)
workQueue
創建時, 會創建 thread pool 作為 queue pool , 每個 queue 又可以放入多個任務, 所以呈現以下結構。
workQueue
發現裡面還多了一條 thread 名叫 worker thread , 其功能是排程 thread 1~3 , 包含 :
利用這樣的作法, 增進 multi-thread CPU 使用率, 不要有很多 thread 明明沒事做, 卻還在運作。
ThreadPool
以下從前天 Task 運行完成, 發現連續任務區有任務開始, 按照步驟陳述
UnsafeQueueCustomWorkItem
把任務放入全域變數 ThreadPoolGlobals
中的 workQueue
workQueue
後被按照 thread 掛載到 thread 的任務區中RequestWorkerThread
此時被呼叫, 會創建 workerThread
。workerThread
檢查各條 thread 的任務區, 使任務區為空的 thread 閒置, 喚醒任務區有任務的 thread 等等....。至於被喚醒的 thread 內的任務如何被觸發, 會在之後說明。
到此 .Net thread pool 的探底算是告一段落, 可以發現, 若是用 linux 的 workQueue 的角度來理解, .Net 的 thread pool 就是實踐了上半部分的 workQueue , 包含維護各個 thread 的 queue, 並且把待執行的任務, 依情況放入各個 thread 的 queue 中。但是 .Net 本身不去排程這些 queue 和他們裡面的任務。
.Net 調用外來方法創建一條排程 thread , 這條排程 thread 會完成 workQueue 的排程任務, 包含
注 : 關於外來排程 thread 的功能, 我僅能猜測, 因為原始碼不好找, 其調用了外部程式。有錯可以直接向我說~~
透過這幾天的努力, 我們了解了 Task 執行完本身任務後處理連續任務區的任務的方法。
明天我們就開始來看看 Task 是怎麼執行本身的任務吧
明天見 !